/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"



//TODO: use the pipes to communicate with worker threads, no need to restart proceseses unless they get killed. different than target command was.

/* prototypes */
void pdlerror(char *dlfunc);
int _call_dl_func(const char *libfname, const char *symbol,
		  ffi_type * args[], void *values[], size_t num_args,
		  ffi_type * ret_type, LLIST_P argslist);
DL_ARG_P _new_dlarg(DL_ARG_TYPE type);

//fork a process for the dynamic library fuzzer.
//name dl_launch() is keeping with command.c (the other target subcommand)
//only function symbols right now.
//TODO: assumed uint8_t values are strngs since that is allthat is supported write now, assumes they are \0 trminated.
//argslist list of type struct dl_arg
bool dl_launch(LLIST_P argslist, const char *libfname, const char *symbol)
{
    int pipe_fds[2];
    pid_t pid;

    debug1_printf("dl_launch: lib: %s\tsymbol: %s\n", libfname, symbol);
    //doing following here in parent so that stderr is not redirected to child's file.

    ffi_type *types[MAX_DL_FUNC_ARGS];	//parallel arrayto values
    void *values[MAX_DL_FUNC_ARGS];
    ffi_type *ret_type;
    int argc;
    LLIST_P walker;
    struct st_dlarg *dlarg;

    for (argc = 0, walker = argslist;
	 walker != NULL && argc < MAX_DL_FUNC_ARGS;
	 walker = walker->next, argc++)
      {
	  dlarg = walker->element;

	  switch (dlarg->type)
	    {
	    case TYPE_CSTRING_PTR:
		types[argc] = &ffi_type_pointer;
		values[argc] = &(dlarg->val.string_val);
		break;
	    case TYPE_GENERIC_PTR:
		types[argc] = &ffi_type_pointer;
		values[argc] = &(dlarg->val.ptr_val);
		break;
	    case TYPE_INT:
		types[argc] = &ffi_type_uint;	//defined in ffi.h to be ffi_type_uint16 or ffi_type_uint32 or ffi_type_uint64, will match with int_val below in all cases.
		values[argc] = &(dlarg->val.int_val);
		break;
	    case TYPE_FLOAT:
		types[argc] = &ffi_type_pointer;
		values[argc] = &(dlarg->val.float_val);
		break;
	    default:
		fprintf(stderr, "unsupported type: %d\n", dlarg->type);
		return false;
	    }

      }

    //TODO allow this to be changed.
    ret_type = &ffi_type_sint;



// doing nothing with the pipe here
    if (pipe(pipe_fds) == -1)
      {
	  perror("pipe()");
	  return false;
      }

    pid = fork();
    switch (pid)
      {
      case -1:
	  perror("dl_launch():fork()");
	  close(pipe_fds[0]);
	  close(pipe_fds[1]);
	  return false;
      case 0:			/* child process */
	  debug1_printf("dl child process: %d\n", getpid());
	  close(pipe_fds[1]);	//close write end

//old comment,line of code: may reuse:
	  //stdin data may be coming from the pipe, may not sendanything but dup always anyway.
	  //dup2(pipe_fds[0], fileno(stdin));// pipe read end to stdin

	  //TODO: error check this
	  _call_dl_func(libfname, symbol, types, values, argc, ret_type,
			argslist);
	  exit(EXIT_SUCCESS);	//for now, TODO:later be a more persistent worker process
      default:			/* parent process (pipe reader) */
	  glob.current_target_processes++;
	  close(pipe_fds[0]);	//close read end
	  //      write(pipe_fds[1], stdin_ar, stdin_len);        //todo:error check
	  close(pipe_fds[1]);	//close write end
	  return true;
      }
}


int _call_dl_func(const char *libfname, const char *symbol,
		  ffi_type * args[], void *values[], size_t num_args,
		  ffi_type * ret_type, LLIST_P _dl_argslist)
{
    int ret = 0;
    void *dl_handle;
    void *sym_addr;

    debug2_printf("Calling %s:%s(num_args=%d)\n", libfname, symbol,
		  num_args);

    //lazy symbol binding (for functions)
    if ((dl_handle = dlopen(libfname, RTLD_LAZY)) == NULL)
      {
	  pdlerror("dlopen");
	  return -1;
      }

    if ((sym_addr = dlsym(dl_handle, symbol)) == NULL)
      {
	  pdlerror("dlsym");
	  ret = -1;
    } else			/* dlsym successful */
      {
	  ffi_cif cif;
	  if (ffi_prep_cif
	      (&cif, FFI_DEFAULT_ABI, num_args, ret_type, args) != FFI_OK)
	    {
		fprintf(stderr, "ffi_prep_cif failed!\n");
		ret = -1;
	  } else		/* == FFI_OK */
	    {

/* any perrors etc above will go to parent's terminal below will goto outputlog */
//todo: make outputlog restore for below oerrirs

/* among other things, dup's stdout,stderr to log files */
		outputlog_dl_chldlog_init(_dl_argslist, symbol);


		//assumes sym_addr is func, ret is return value of func
		//ret is (void *).
		ffi_call(&cif, sym_addr, &ret, values);

	    }

      }


    if (dlclose(dl_handle) != 0)	//non-zero on error
      {
	  pdlerror("dlclose");
	  return -1;
      }

    return ret;
}


void pdlerror(char *dlfunc)
{
    char *err = dlerror();	//last error
    fprintf(stderr, "%s(): %s\n", dlfunc, (err == NULL) ? "<null>" : err);
}

char *dlarg_type_to_str(DL_ARG_TYPE type)
{
    switch (type)
      {
      case TYPE_CSTRING_PTR:
	  return "TYPE_CSTRING_PTR";
      case TYPE_GENERIC_PTR:
	  return "TYPE_GENERIC_PTR";
      case TYPE_INT:
	  return "TYPE_INT";
      case TYPE_FLOAT:
	  return "TYPE_FLOAT";
      default:
	  return "unknown";
      }
}

DL_ARG_P new_dlarg_cstring_ptr(char *val)
{
    DL_ARG_P dlarg = _new_dlarg(TYPE_CSTRING_PTR);
    dlarg->val.string_val = val;
    return dlarg;
}

DL_ARG_P new_dlarg_generic_ptr(void *val, size_t ptr_len)
{
    DL_ARG_P dlarg = _new_dlarg(TYPE_GENERIC_PTR);
    dlarg->val.ptr_val = val;
    dlarg->ptr_len = ptr_len;
    return dlarg;
}

DL_ARG_P new_dlarg_int(int val)
{
    DL_ARG_P dlarg = _new_dlarg(TYPE_INT);
    dlarg->val.int_val = val;
    return dlarg;
}

DL_ARG_P new_dlarg_float(float val)
{
    DL_ARG_P dlarg = _new_dlarg(TYPE_FLOAT);
    dlarg->val.float_val = val;
    return dlarg;
}

DL_ARG_P _new_dlarg(DL_ARG_TYPE type)
{
    DL_ARG_P dlarg = checked_malloc(sizeof(struct st_dlarg));
    memset(dlarg, 0, sizeof(struct st_dlarg));	/* not necessary, but best to zero out ptr_len etc */
    dlarg->type = type;
    return dlarg;
}
